package Script;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import Calculate.ScriptFormat;
import Error.ScriptError;
import Language.Language;

import interfaces.ErrorReport;
import interfaces.ScriptReader;
import interfaces.TextScriptArea;
import interfaces.ErrorReport.ErrorType;

public class JScriptReader implements ScriptReader {
	private File 	scriptTextFile;
	private String 	pointerRegex = "(<PT)([0]*?)([1-9]{1}?[0-9]{0,9}?)(>)";
	private String 	charset;
	private int 	highestPointer;
	
	public JScriptReader(File scriptTextFile, String charset) {
		this.scriptTextFile = scriptTextFile;
		this.highestPointer = -1;
		
		if (charset == null) {
			this.charset = "UTF-16";
		} else {
			this.charset = charset;
		}
	}

	/**
	 * Reads a script file and divides it into
	 * TextSciptArea objects. Only reads data starting
	 * from the line below <SCRIPT START>.
	 * @return Collection of text areas from the script, or null
	 * 		   if an error occured during script reading.
	 * @throws FileNotFoundException 
	 */
	public ArrayList<TextScriptArea> read(int fixedLength) 
						throws FileNotFoundException, NumberFormatException {
		Pattern pattern;
		Matcher matcher;
		ArrayList<Integer> tempPointers;
		int highestPointer = -1;
		
		int lastEnd = 0; // End position for a pattern match, exclusive
		ArrayList<TextScriptArea> textAreas = new ArrayList<TextScriptArea>();
		String text = null;
		 
		
		/* Get all text from script. */
		text = TextReader.getText(scriptTextFile, charset);

		/* Check if it worked. */
		if (text.equals(null)) {
			return null; //Error occured
			
		} else if (fixedLength > 0) {

			/*
			 * If the script contains fixed length strings,
			 * each string is read into its own text area
			 * (without any pointers) and returned. The
			 * rest of this method is skipped.
			 */
			return readFixedLength(fixedLength);
			
		} else {
			
			pattern = Pattern.compile(pointerRegex);
			matcher = pattern.matcher(text);
			
			tempPointers = new ArrayList<Integer>();
			
			/* Get all matches. */
			while (matcher.find()) {
				
				/*
				 * For each pointer found using the pattern, its number is
				 * added to a list. When a pointer is found, that does not
				 * come immedeatly after the last, the text between them
				 * is dumped to a textArea, and all previous pointers
				 * are added to it before the list is cleared.
				 */
				if (lastEnd != matcher.start()) {
					textAreas.add(new JTextScriptArea(
									text.substring(lastEnd, matcher.start())));
					addPointersToTextArea(tempPointers, 
										  textAreas.get(textAreas.size() - 1));
					
					/* Clear pointers. */
					tempPointers = new ArrayList<Integer>();
				}
				
					tempPointers.add(Integer.parseInt(matcher.group(3)));
				
				if (Integer.parseInt(matcher.group(3)) > highestPointer) {
					highestPointer = Integer.parseInt(matcher.group(3));
				}
				
				/* Set the current position. */
				lastEnd = matcher.end();
			}

			/* 
			 * Add the text after the last pointer to a new text area, and
			 * add the last set of pointers to it. If there's no text left,
			 * an empty string is used.
			 */
			if (lastEnd < text.length()) {
				textAreas.add(new JTextScriptArea(text.substring(lastEnd)));
				
			} else {
				textAreas.add(new JTextScriptArea(""));
			}
			
			addPointersToTextArea(tempPointers, 
								  textAreas.get(textAreas.size() - 1));
			
		}
		
		/* Update the highest pointer for this reader. */
		this.highestPointer = highestPointer;
		
		return textAreas;
	}

	/**
	 * Returns the highest found pointer number in this script.
	 */
	public int getNumberOfPointers() {
		return highestPointer;
	}
	
	/**
	 * Validates the script file. Checks for:
	 * 
	 * 		.duplicate pointers
	 * 		.pointers with more than 9 numbers
	 * 
	 * 
	 * @return List of all errors, with description and line in script.
	 * @throws FileNotFoundException 
	 */
	@SuppressWarnings("unchecked")
	public ArrayList<ErrorReport> isValid() throws FileNotFoundException {
		ArrayList<ErrorReport> errors = new ArrayList<ErrorReport>();
		String scriptText;
		String[] scriptLines;
		ArrayList<String> lines;
		ArrayList<String> sendLines;
		Pattern pattern;
		Matcher matcher;
		String temp;
		String temp2;
		ErrorReport rep;
		int n; // Tracks which line in the file is being checked
		int lineNumber = 0; // Stores a line with a duplicate pointer in it.
			
		/* Get the text and split it into lines. */
		scriptText = TextReader.getText(scriptTextFile, charset);
		scriptLines = scriptText.split("\r\n|\r|\n");
		lines = new ArrayList<String>();
			
		/* Add the lines to a collection. */
		for (int i = 0; i < scriptLines.length; i++) {
			lines.add(scriptLines[i]);
		}
			
		/* 
		 * Check each line in the script and check if it contains any
		 * invalid elements.
		 */
		n = 1; // Starting row = 1
			
		while (lines.size() > 0) {
			pattern = Pattern.compile(pointerRegex);
			matcher = pattern.matcher(lines.get(0));
				
			/* 
			 * Before any line is removed, check if it 
			 * contains invalid pointers.
			 */
			if (invalidLengthPointerExists(lines.get(0))) {
				rep = new ScriptError(Language.largePointer, lines.get(0), n, ErrorType.ERROR);
				errors.add(rep);
			}
				
			/* Check for valid pointers to investigate. */
			if (matcher.find()) {
					
				/* 
				 * Remove the match from the set, to avoid it
				 * getting caught in later searches.
				 */
				temp2 = lines.get(0);
				lines.set(0, lines.get(0).substring(matcher.end()));
				
				/*
				 * Error handling for a pointer number value starts here.
				 */
				
				/* Check if this number is higher than the max integer value. */
				if (Long.parseLong(matcher.group(3)) > Integer.MAX_VALUE) {
					rep = new ScriptError(Language.largePointerNumber, temp2, n, ErrorType.ERROR);
					errors.add(rep);
				}
				
				/* Check if there's a duplicate. */
				temp = "(<PT)([0]*?)(" + matcher.group(3) + ")(>)";
				sendLines = (ArrayList<String>) lines.clone();

				lineNumber = containsPointer(temp, sendLines, n);
					
				if (lineNumber > 0) {
						
					/* If duplicate pointer is found, submit new error. */
					rep = new ScriptError(Language.duplicatePointer, temp2, lineNumber, ErrorType.ERROR);
					errors.add(rep);
				}

				
			} else {
				lines.remove(0);
					
				/* Since a line has been removed, increment line counter. */
				n++;
			}
		}

		return errors;
	}
	
	/**
	 * Reads a set of fixed-length strings from a text script into
	 * text script areas (one for each string) without pointers.
	 * A string is considered terminated by an <END> tag, and the
	 * method will continue to read a single string until it
	 * finds one. Each string that surpasses the fixed length will
	 * be shortened before returned. If the last string does not have
	 * a tag, it will be discarded.
	 * 
	 * @param fixedLength Length of each fixed-length entry. 
	 * @param padByte
	 * @return Set of fixed-length strings. Null if strings contains pointers.
	 * @throws FileNotFoundException
	 */
	private ArrayList<TextScriptArea> readFixedLength(int fixedLength) throws FileNotFoundException {
		Pattern 	pattern;
		Matcher 	matcher;
		String 		content = TextReader.getText(scriptTextFile, charset);
		int			lastPos;
		String		flStr;
		
		ArrayList<String> text = new ArrayList<String>();
		ArrayList<TextScriptArea> textAreas = new ArrayList<TextScriptArea>();
		
		text.add(content);
		
		/* Return null if there's pointers present. */
		if (containsPointer(pointerRegex, text, 1) != -1) {
			return null;
		}
		
		pattern = Pattern.compile(ScriptFormat.getEndtag());
		matcher = pattern.matcher(content);
		lastPos = 0;
		
		/* 
		 * Search for <END> tags and put the content between them
		 * into separate text area.
		 */
		while (matcher.find()) {
			flStr = content.substring(lastPos, matcher.start());
			
			/* Remove newline and tabs. */
			flStr = flStr.replaceAll("\\n","");
			flStr = flStr.replaceAll("\\t","");
			flStr = flStr.replaceAll("\\r","");
			flStr = flStr.replaceAll(System.getProperty("line.separator"),"");
			
			if (flStr.length() > fixedLength) {
				flStr = flStr.substring(0, fixedLength);
			}
			
			textAreas.add(new JTextScriptArea(flStr));
			
			/* Set position to the offset after the current match. */
			lastPos = matcher.end(); 
		}
		
		return textAreas;
	}
	

	/**
	 * Checks if a collection of strings contains a certain regular
	 * expression, and returns the line number of that expression.
	 * 
	 * @param regex Expression to search for. Must contain 4 groups:
	 * 
	 * 					(<PT)([0]+?)([1-9]{1,10}?)(>)
	 * 
	 * @param scriptLines Strings to search in.
	 * @param line Line number currently being searched.
	 * @return Line number the expression is found in, or -1 if no
	 * 		   match is found.
	 */
	private int containsPointer(String pointerRegex, ArrayList<String> scriptLines, 
								int line) {
		Pattern pattern;
		Matcher matcher;
		
		/* Check if there's any text to search. */
		if (scriptLines.size() > 0) {
			pattern = Pattern.compile(pointerRegex);
			matcher = pattern.matcher(scriptLines.get(0));
			
			/* 
			 * If there's a match, search the rest of the strings
			 * for any duplicates of this particular match.
			 */
			if (matcher.find()) {
				
				return line;
				
			} else {
				scriptLines.remove(0);
				return containsPointer(pointerRegex, scriptLines, line + 1);
			}
			
		} else {
			
			/* No strings left to search, no matches found. */
			return -1;
		}

	}
	
	/**
	 * Checks if the length of the pointer number is valid, based on
	 * two regex'es. One valid, and one invalid.
	 * A pointer number can only be 9 characters long
	 * (not counting leading zeros).
	 * @param exp String to validate.
	 * @param validExp Regular expression that matches a valid string.
	 * @param invalidExp Regular expression that matches an unvalid string.
	 * @return True if string is valid, otherwise false.
	 */
	private boolean invalidLengthPointerExists(String scriptLine) {
		Pattern pattern = Pattern.compile("(<PT)([0]*?)([1-9]{1}?[0-9]{10,}?)(>)");
		Matcher matcher;
		
		/* Check valid formatting. */
		matcher = pattern.matcher(scriptLine);
		
		if (matcher.find()) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Takes a set of pointer numbers and adds them to a TextScriptArea.
	 */
	private void addPointersToTextArea(ArrayList<Integer> pointers, 
												TextScriptArea text) {
		
		if (pointers.size() > 0) {
			for (Integer i : pointers) {
				text.addPointer(i);
			}
		}
		
	}

}
